#include "net.h"
#include "ip.h"
#include "ethernet.h"
#include "arp.h"
#include "icmp.h"

// ip数据包的id字段
uint16_t ip_id = 0;

/**
 * @brief 处理一个收到的数据包
 * 
 * @param buf 要处理的数据包
 * @param src_mac 源mac地址
 */
void ip_in(buf_t *buf, uint8_t *src_mac)
{
    // 数据包长度小于IP头长度，丢弃之
    if(buf->len < sizeof(ip_hdr_t))     return;
    ip_hdr_t* ip = (ip_hdr_t *)buf->data; 

    // 检查头部版本号、总长度、头长度字段
    if(ip->version != IP_VERSION_4)                             return;
    if(swap16(ip->total_len16) > buf->len)                      return;
    if(ip->hdr_len*IP_HDR_LEN_PER_BYTE != sizeof(ip_hdr_t))     return;

    // 检查头部校验和
    uint16_t temp = ip->hdr_checksum16;
    ip->hdr_checksum16 = 0;
    if(temp != checksum16((uint16_t *)ip, sizeof(ip_hdr_t)))    return;
    ip->hdr_checksum16 = temp;

    // 如果目的IP地址不是本机的IP地址，则直接丢弃
    if(memcmp(ip->dst_ip, net_if_ip, NET_IP_LEN) != 0)          return; 

    // 如果数据包的长度大于IP报文头总长度字段，则去除填充字段
    buf_remove_padding(buf, buf->len - swap16(ip->total_len16));

    // 去除IP报文头
    buf_remove_header(buf, sizeof(ip_hdr_t));

    // 如果是不能识别的协议类型
    if(net_in(buf, ip->protocol, ip->src_ip) == -1){
        // 复原IP报文头
        buf_add_header(buf, sizeof(ip_hdr_t));
        memcpy(buf->data, ip, sizeof(ip_hdr_t));
 
        // 发送ICMP协议不可达报文 
        icmp_unreachable(buf, ip->src_ip, ICMP_CODE_PROTOCOL_UNREACH);
    }
}

/**
 * @brief 处理一个要发送的ip分片
 * 
 * @param buf 要发送的分片
 * @param ip 目标ip地址
 * @param protocol 上层协议
 * @param id 数据包id
 * @param offset 分片offset，必须被8整除
 * @param mf 分片mf标志，是否有下一个分片
 */
void ip_fragment_out(buf_t *buf, uint8_t *ip, net_protocol_t protocol, int id, uint16_t offset, int mf)
{
    // 为这个分片初始化ip报文头
    buf_add_header(buf, sizeof(ip_hdr_t));

    // 填写ip数据报头部字段
    ip_hdr_t* iph = (ip_hdr_t*)buf->data;
    iph->hdr_len = sizeof(ip_hdr_t) / IP_HDR_LEN_PER_BYTE;
    iph->version = IP_VERSION_4;
    iph->tos = 0;
    iph->total_len16 = swap16(buf->len);
    iph->id16 = swap16(id);
    iph->flags_fragment16 = swap16((mf << 13) | (offset >> 3));
    iph->ttl = IP_DEFALUT_TTL;
    iph->protocol = protocol;
    memcpy(iph->src_ip, net_if_ip, NET_IP_LEN);
    memcpy(iph->dst_ip, ip, NET_IP_LEN);

    // 填完后再计算校验和
    iph->hdr_checksum16 = 0;
    iph->hdr_checksum16 = checksum16((uint16_t *)iph, sizeof(ip_hdr_t));

    // 向下层发送ip数据报
    arp_out(buf, ip);
}

/**
 * @brief 处理一个要发送的ip数据包
 * 
 * @param buf 要处理的包
 * @param ip 目标ip地址
 * @param protocol 上层协议
 */
void ip_out(buf_t *buf, uint8_t *ip, net_protocol_t protocol)
{
    // 如果数据包长没有超过IP协议的MTU 直接将整个报文当做一个分片发送
    if(buf->len <= ETHERNET_MAX_TRANSPORT_UNIT - sizeof(ip_hdr_t))  return ip_fragment_out(buf, ip, protocol, ip_id++, 0, 0);

    // 如果包长超过IP_MTU ，则分片发送
    uint16_t offset = 0;
    
    // 除最后一个分片外，每个片大小都是ip_MTU
    while (buf->len - offset > ETHERNET_MAX_TRANSPORT_UNIT - sizeof(ip_hdr_t))
    {
        buf_t ip_buf;
        buf_init(&ip_buf, ETHERNET_MAX_TRANSPORT_UNIT - sizeof(ip_hdr_t));
        memcpy(ip_buf.data, buf->data + offset, ETHERNET_MAX_TRANSPORT_UNIT - sizeof(ip_hdr_t));
        ip_fragment_out(&ip_buf, ip, protocol, ip_id, offset, 1);
        offset += (ETHERNET_MAX_TRANSPORT_UNIT - sizeof(ip_hdr_t));
    }
    
    // 处理并发送最后一个分片
    buf_t ip_buf;
    buf_init(&ip_buf, buf->len - offset);
    memcpy(ip_buf.data, buf->data + offset, buf->len - offset);
    ip_fragment_out(&ip_buf, ip, protocol, ip_id++, offset, 0);
}

/**
 * @brief 初始化ip协议
 * 
 */
void ip_init()
{
    net_add_protocol(NET_PROTOCOL_IP, ip_in);
}